#include "SerialPort.h"
#include <stdio.h>
#include <iostream>

SerialPort::SerialPort()
    : hSerial_(INVALID_HANDLE_VALUE)
    , hReadEvent_(NULL)
    , hTimerEvent_(NULL)
    , bRunning_(false)
    , baudRate_(0)
    , timeoutMs_(100) {

    ev_parser_init(&parser_, this, &SerialPort::ParserCallback);

    lastReceiveTime_ = std::chrono::steady_clock::now();
}

SerialPort::~SerialPort() {
    Close();
}

void SerialPort::ParserCallback(const ev_parser_t* parser, const ev_packet_t* packet) {
    SerialPort* instance = static_cast<SerialPort*>(const_cast<void*>(parser->arg));
    if (instance && instance->bRunning_) {
        {
            std::lock_guard<std::mutex> lock(instance->timeMutex_);
            instance->lastReceiveTime_ = std::chrono::steady_clock::now();
        }

        MessagePtr message = std::make_shared<ev_packet_t>();
        *message = *packet;

        {
            std::lock_guard<std::mutex> lock(instance->queueMutex_);
            instance->messageQueue_.push(message);
        }
        instance->queueCondition_.notify_one();
    }
}

bool SerialPort::Open(const std::string& portName, int baudRate) {
    if (IsOpen()) {
        Close();
    }

    portName_ = portName;
    baudRate_ = baudRate;

    char fullPortName[32];
    sprintf_s(fullPortName, "\\\\.\\%s", portName.c_str());

    hSerial_ = CreateFileA(
        fullPortName,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
        NULL
    );

    if (hSerial_ == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open serial port: " << GetLastError() << std::endl;
        return false;
    }

    DCB dcb = { 0 };
    dcb.DCBlength = sizeof(dcb);

    if (!GetCommState(hSerial_, &dcb)) {
        std::cerr << "Failed to get comm state: " << GetLastError() << std::endl;
        Close();
        return false;
    }

    dcb.BaudRate = baudRate;
    dcb.ByteSize = 8;
    dcb.StopBits = ONESTOPBIT;
    dcb.Parity = NOPARITY;
    dcb.fBinary = TRUE;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;
    dcb.fRtsControl = RTS_CONTROL_DISABLE;

    if (!SetCommState(hSerial_, &dcb)) {
        std::cerr << "Failed to set comm state: " << GetLastError() << std::endl;
        Close();
        return false;
    }

    COMMTIMEOUTS timeouts = { 0 };
    timeouts.ReadIntervalTimeout = MAXDWORD;
    timeouts.ReadTotalTimeoutConstant = 0;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;

    if (!SetCommTimeouts(hSerial_, &timeouts)) {
        std::cerr << "Failed to set timeouts: " << GetLastError() << std::endl;
        Close();
        return false;
    }

    PurgeComm(hSerial_, PURGE_TXCLEAR | PURGE_RXCLEAR);

    hReadEvent_ = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!hReadEvent_) {
        std::cerr << "Failed to create event: " << GetLastError() << std::endl;
        Close();
        return false;
    }

    hTimerEvent_ = CreateWaitableTimer(NULL, TRUE, NULL);
    if (!hTimerEvent_) {
        std::cerr << "Failed to create timer event: " << GetLastError() << std::endl;
        Close();
        return false;
    }

    LARGE_INTEGER dueTime;
    dueTime.QuadPart = -0;
    if (!SetWaitableTimer(hTimerEvent_, &dueTime, 50, NULL, NULL, FALSE)) {
        std::cerr << "Failed to set timer: " << GetLastError() << std::endl;
        Close();
        return false;
    }

    ev_parser_reset(&parser_);

    lastReceiveTime_ = std::chrono::steady_clock::now();

    bRunning_ = true;
    readThread_ = std::make_unique<std::thread>(&SerialPort::ReadThread, this);

    return true;
}

void SerialPort::Close() {
    bRunning_ = false;

    if (readThread_ && readThread_->joinable()) {
        readThread_->join();
        readThread_.reset();
    }

    if (hTimerEvent_) {
        CancelWaitableTimer(hTimerEvent_);
        CloseHandle(hTimerEvent_);
        hTimerEvent_ = NULL;
    }

    if (hReadEvent_) {
        CloseHandle(hReadEvent_);
        hReadEvent_ = NULL;
    }

    if (hSerial_ != INVALID_HANDLE_VALUE) {
        PurgeComm(hSerial_, PURGE_TXCLEAR | PURGE_RXCLEAR);
        CloseHandle(hSerial_);
        hSerial_ = INVALID_HANDLE_VALUE;
    }

    {
        std::lock_guard<std::mutex> lock(queueMutex_);
        while (!messageQueue_.empty()) {
            messageQueue_.pop();
        }
    }
}

bool SerialPort::IsOpen() const {
    return hSerial_ != INVALID_HANDLE_VALUE;
}

bool SerialPort::SendData(const uint8_t* data, size_t length) {
    if (!IsOpen() || !data || length == 0) {
        return false;
    }

    OVERLAPPED ov = { 0 };
    ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    DWORD bytesWritten = 0;
    bool result = WriteFile(hSerial_, data, static_cast<DWORD>(length), &bytesWritten, &ov);

    if (!result && GetLastError() == ERROR_IO_PENDING) {
        WaitForSingleObject(ov.hEvent, INFINITE);
        GetOverlappedResult(hSerial_, &ov, &bytesWritten, FALSE);
        result = true;
    }

    CloseHandle(ov.hEvent);
    return result && (bytesWritten == length);
}

bool SerialPort::SendData(const ev_packet_t* pkt) {
    if (!IsOpen() || !pkt) {
        return false;
    }

    uint8_t buffer[EV_PACKET_MAX_LEN];
    uint32_t length = EV_PACKET_MAX_LEN;

    if (ev_packet_make(pkt, buffer, &length) != 0) {
        std::cerr << "Failed to make packet" << std::endl;
        return false;
    }

    return SendData(buffer, length);
}

bool SerialPort::GetMessage(MessagePtr& message, DWORD timeoutMs) {
    std::unique_lock<std::mutex> lock(queueMutex_);

    if (timeoutMs == INFINITE) {
        queueCondition_.wait(lock, [this]{ return !messageQueue_.empty() || !bRunning_; });
        if (!messageQueue_.empty()) {
            message = messageQueue_.front();
            messageQueue_.pop();
            return true;
        }
        return false;
    } else {
        bool notified = queueCondition_.wait_for(lock, std::chrono::milliseconds(timeoutMs),
            [this]{ return !messageQueue_.empty() || !bRunning_; });

        if (notified && !messageQueue_.empty()) {
            message = messageQueue_.front();
            messageQueue_.pop();
            return true;
        }
        return false;
    }
}

bool SerialPort::PeekMessage(MessagePtr& message) {
    std::lock_guard<std::mutex> lock(queueMutex_);
    if (!messageQueue_.empty()) {
        message = messageQueue_.front();
        return true;
    }
    return false;
}

size_t SerialPort::GetMessageCount() const {
    std::lock_guard<std::mutex> lock(queueMutex_);
    return messageQueue_.size();
}

void SerialPort::CheckTimeout() {
    auto now = std::chrono::steady_clock::now();
    std::chrono::milliseconds elapsed;

    {
        std::lock_guard<std::mutex> lock(timeMutex_);
        elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastReceiveTime_);
    }

    if (elapsed.count() >= timeoutMs_) {
        ev_parser_reset(&parser_);
        {
            std::lock_guard<std::mutex> lock(timeMutex_);
            lastReceiveTime_ = now;
        }
        std::cout << "Parser reset due to timeout (" << timeoutMs_ << "ms)" << std::endl;
    }
}

void SerialPort::ReadThread() {
    OVERLAPPED ov = { 0 };
    ov.hEvent = hReadEvent_;

    std::vector<uint8_t> buffer(4096);
    DWORD bytesRead = 0;

    HANDLE events[2] = { hReadEvent_, hTimerEvent_ };

    while (bRunning_) {
        if (!ReadFile(hSerial_, buffer.data(), static_cast<DWORD>(buffer.size()), &bytesRead, &ov)) {
            if (GetLastError() != ERROR_IO_PENDING) {
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
                continue;
            }
        }

        DWORD waitResult = WaitForMultipleObjects(2, events, FALSE, INFINITE);

        switch (waitResult) {
            case WAIT_OBJECT_0:
                if (GetOverlappedResult(hSerial_, &ov, &bytesRead, FALSE)) {
                    if (bytesRead > 0) {
                        {
                            std::lock_guard<std::mutex> lock(timeMutex_);
                            lastReceiveTime_ = std::chrono::steady_clock::now();
                        }
                        ev_parser_feed(&parser_, buffer.data(), bytesRead);
                    }
                }
                ResetEvent(hReadEvent_);
                break;

            case WAIT_OBJECT_0 + 1:
                CheckTimeout();
                LARGE_INTEGER dueTime;
                dueTime.QuadPart = -50 * 10000;
                SetWaitableTimer(hTimerEvent_, &dueTime, 50, NULL, NULL, FALSE);
                break;

            default:
                break;
        }
    }
}
